package api

import (
	"fmt"
	"github.com/bytedance/sonic"
	"github.com/duke-git/lancet/v2/fileutil"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/iancoleman/strcase"
	"github.com/osteele/liquid"
	"github.com/zeromicro/ddl-parser/parser"
	"os"
	path2 "path"
	"path/filepath"
	"smoot-code/service/data"
	"smoot-code/service/types"
	"smoot-code/service/util"
	"strconv"
	"strings"
	"sync"
)

type GenerateApi struct {
	data data.Data
}

var generate *GenerateApi
var onceGenerate sync.Once

func (gen *GenerateApi) GenCode(key string, name string) types.Resp {
	success := true
	msg := "Success"

	srcPath := path2.Join(types.GetConfig().CodegenUrl, "template", key)
	dstPath := path2.Join(types.GetConfig().GenerateUrl, name)
	flag := fileutil.IsExist(dstPath)
	if flag {
		err := os.RemoveAll(dstPath)
		if err != nil {
			return types.Resp{}
		}
	}
	err := fileutil.CopyDir(srcPath, dstPath)
	if err != nil {
		success = false
	}
	configData, err := getConfigData(key)
	if err != nil {
		fmt.Printf("%+v", err)
		success = false
		msg = err.Error()
	}
	jsonData, err := getVarData(key, configData)
	if err != nil {
		success = false
		msg = err.Error()
	}
	engine := liquid.NewEngine()
	err = util.WalkFile(dstPath, func(path string, info os.FileInfo) error {
		if !info.IsDir() || !util.IsBinaryFile(path) {
			err := fillFile(path, info, engine, jsonData)
			if err != nil {
				return err
			}
		}
		return nil
	})

	err = fillFileName(dstPath, engine, jsonData)

	if err != nil {
		return types.Resp{}
	}
	return types.Resp{
		Success: success,
		Data:    key,
		Msg:     msg,
	}
}
func fillFileName(path string, engine *liquid.Engine, jsonData map[string]any) error {

	files, err := os.ReadDir(path)
	if err != nil {
		return err
	}

	for _, file := range files {
		fmt.Printf("file%s\n", file.Name())
		if file.IsDir() {
			next := filepath.Join(path, file.Name())
			err := fillFileName(next, engine, jsonData)
			if err != nil {
				return err
			}

			out, err := engine.ParseAndRenderString(file.Name(), jsonData)
			if err != nil {
				fmt.Printf("66%+v", err)
				return err
			}
			if out != path {
				err := os.Rename(path2.Join(path, file.Name()), path2.Join(path, out))
				if err != nil {
					fmt.Printf("55%+v", err)
					return err
				}
				return nil
			}

		} else {
			out, err := engine.ParseAndRenderString(file.Name(), jsonData)
			if err != nil {
				fmt.Printf("66%+v", err)
				return err
			}
			if out != file.Name() {
				pathName := filepath.Join(path, file.Name())
				outName := filepath.Join(path, out)
				err := os.Rename(pathName, outName)
				if err != nil {
					fmt.Printf("55%+v", err)
					return err
				}
				return nil
			}
		}

	}
	return nil
}
func fillFile(path string, info os.FileInfo, engine *liquid.Engine, jsonData map[string]any) error {
	// 循环文件开头
	if strings.HasPrefix(info.Name(), "{% for") {
		filePath := strings.TrimSuffix(path, info.Name())
		fileData, _ := fileutil.ReadFileToString(path)
		ext := filepath.Ext(path)
		fileName := strings.TrimSuffix(info.Name(), ext)

		out, err := engine.ParseAndRenderString(fileName, jsonData)
		if err != nil {
			fmt.Printf("1111%+v", err)
			return err
		}
		fileNames := strings.Split(strings.Trim(out, " "), ";")
		for _, item := range fileNames {
			if item == "" {
				continue
			}
			paths := path2.Join(filePath, item+ext)
			fileutil.CreateFile(paths)
			str := `{% assign fileName =  "` + item + `" %} ` + "\n" + fileData
			out, err := engine.ParseAndRenderString(str, jsonData)
			if err != nil {
				fmt.Printf("222%+v", err)
				return nil
			}
			errs := fileutil.WriteStringToFile(paths, out, false)
			if errs != nil {
				return err
			}
		}
		defer func(name string) {
			fmt.Printf("name%s\n", name)
			err := os.Remove(name)
			if err != nil {
				fmt.Printf("%+v", err)
			}

		}(path)
		return nil
	} else {
		fileData, _ := fileutil.ReadFileToString(path)
		out, err := engine.ParseAndRenderString(fileData, jsonData)
		if err != nil {
			return err
		}
		errs := fileutil.WriteStringToFile(path, out, false)
		if errs != nil {
			return err
		}
	}
	return nil
}

func getVarData(key string, configData map[string]any) (map[string]any, error) {

	jsonData := make(map[string]interface{})
	var sqlType map[string]any = nil
	if configData["dbType"] != nil {
		m, ok := configData["dbType"].(map[string]any)
		if !ok {
			return nil, fmt.Errorf("sqlType 类型转换失败")
		}
		sqlType = m
	}

	var dbConfig map[string]any = nil
	if configData["dbConfig"] != nil {
		m, ok := configData["dbConfig"].(map[string]any)
		if !ok {
			return nil, fmt.Errorf("dbConfig 类型转换失败")
		}
		dbConfig = m
	}

	path := path2.Join(types.GetConfig().CodegenUrl, "var", key)
	err := util.WalkFile(path, func(path2 string, info os.FileInfo) error {
		if info.IsDir() {
			return nil
		}
		file, err := os.ReadFile(path2)
		if err != nil {
			return err
		}

		ext := filepath.Ext(path2)

		fileName := strings.TrimSuffix(info.Name(), ext)

		if ext == ".json" {
			varData := make(map[string]any)
			err = sonic.Unmarshal(file, &varData)
			if err != nil {
				fmt.Printf("%+v", err)
				return err
			}
			jsonData[fileName] = varData
		} else if ext == ".sql" {

			abs, err := filepath.Abs(path2)
			if err != nil {
				return err
			}

			parse, err := parser.NewParser().From(abs)
			if err != nil {
				return err
			}
			dbMap := make(map[string]any, len(parse))
			models := make(map[string]types.Model, len(parse))
			tables := make([]string, len(parse))
			modelToTable := make(map[string]string, len(parse))
			for index, item := range parse {
				model := types.Model{
					Name:      item.Name,
					TableName: item.Name,
					Fields:    make([]types.Field, len(item.Columns)),
				}
				if dbConfig["tableName"] == "ToLowerCamel" {
					model.Name = strcase.ToLowerCamel(item.Name)
				} else if dbConfig["tableName"] == "ToCamel" {
					model.Name = strcase.ToCamel(item.Name)
				}
				for index, column := range item.Columns {
					model.Fields[index] = types.Field{
						Name:       column.Name,
						DbName:     column.Name,
						DbType:     column.DataType.Type(),
						Type:       sqlType[strconv.Itoa(column.DataType.Type())],
						Comment:    column.Constraint.Comment,
						PrimaryKey: slice.IndexOf(item.Constraints[0].ColumnPrimaryKey, column.Name) > -1,
					}

					if dbConfig["fieldName"] == "ToLowerCamel" {
						model.Fields[index].Name = strcase.ToLowerCamel(column.Name)
					} else if dbConfig["fieldName"] == "ToCamel" {
						model.Fields[index].Name = strcase.ToCamel(column.Name)
					}

				}
				tables[index] = model.Name
				models[model.Name] = model
				modelToTable[model.Name] = model.TableName
			}
			dbMap["tables"] = tables
			dbMap["models"] = models
			dbMap["mtot"] = modelToTable
			jsonData[fileName] = dbMap

		}
		return nil
	})
	if err != nil {
		fmt.Printf("%+v", err)
		return nil, err
	}
	return jsonData, nil
}

func getConfigData(key string) (map[string]interface{}, error) {
	path := path2.Join(types.GetConfig().CodegenUrl, "config", key, "config.json")
	dat, err := os.ReadFile(path)
	varData := make(map[string]interface{})
	err = sonic.Unmarshal(dat, &varData)
	if err != nil {
		fmt.Printf("%+v", err)
		return nil, err
	}
	return varData, nil
}

func NewGenerateApi(datas data.Data) *GenerateApi {
	if generate == nil {
		onceGenerate.Do(func() {
			generate = &GenerateApi{datas}

		})
	}
	return generate
}
